package com.diy.sigmund.diygateway.fallback;

import com.diy.sigmund.diycommon.entity.ResultMsg;
import com.diy.sigmund.diycommon.entity.constant.GatewayConstant;
import com.diy.sigmund.diycommon.entity.enumerate.ResponseEnum;
import com.diy.sigmund.diycommon.util.json.JacksonUtil;
import com.diy.sigmund.diygateway.filter.CustomLogFilter;
import com.netflix.zuul.context.RequestContext;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.time.LocalDateTime;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

/**
 * 自定义熔断fallback，Zuul的容错与回退，主要是针对服务不可用的场景的自定义响应
 * https://blog.csdn.net/qq_27384769/article/details/82991261
 *
 * @author ylm-sigmund
 * @since 2021/9/2 10:08
 */
@Component
public class CustomFallbackProvider implements FallbackProvider {

    @Autowired
    private CustomLogFilter customLogFilter;
    private static final Logger log = LoggerFactory.getLogger(CustomFallbackProvider.class);

    /**
     * 此回退将用于的路线。
     *
     * @return 回退将用于的路由。
     */
    @Override
    public String getRoute() {
        // 匹配微服务名字，*表示匹配所有
        return "*";
    }

    /**
     * 根据执行失败的原因提供回退响应。
     * 参数：route – 回退的路线
     * Cause – main 方法失败的原因，可能为null
     * 回报：后备响应
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        RequestContext currentContext = RequestContext.getCurrentContext();
        StringBuilder sbRsp = new StringBuilder(1000);
        long costTime = Duration.between((LocalDateTime) currentContext.get(GatewayConstant.PRE_START_TIME),
                LocalDateTime.now()).toMillis();
        sbRsp.append("\n-----------------------------网关层响应日志结束调用:-----------------------------\n");
        sbRsp.append("URI       : ").append(request.getMethod()).append(" ").append(request.getProtocol()).append(" ")
                .append(request.getRequestURI()).append(customLogFilter.buildParams(request)).append("\n");
        sbRsp.append("RequestBody    : ").append(customLogFilter.buildBody(request)).append("\n");
        sbRsp.append("网关全局请求响应耗时  : ").append(costTime).append(" ms\n");

        log.warn("网关服务进行熔断降级：{},{},{}", route, cause.getMessage(), sbRsp);
        return new ClientHttpResponse() {
            /**
             * 获取 {@link HttpStatus} 状态代码作为HttpStatus枚举值。
             * 对于 HttpStatus 不支持的状态代码，请改用{@link #getRawStatusCode()}
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                // 当fallback时返回给调用者的状态码
                return HttpStatus.SERVICE_UNAVAILABLE;
            }

            /**
             * 以整数形式获取 HTTP 状态代码（可能是非标准的且无法通过HttpStatus枚举解析）。
             */
            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }

            /**
             * 获取响应的 HTTP 状态文本。
             */
            @Override
            public String getStatusText() throws IOException {
                // 状态码的文本形式
                return null;
            }

            /**
             * 关闭此响应，释放所有创建的资源。
             */
            @Override
            public void close() {

            }

            /**
             * 将消息的正文作为输入流返回。
             */
            @Override
            public InputStream getBody() throws IOException {
                // 定义响应体，当且仅当 不设置setSendZuulResponse为false时，会返回如下数据
                // return    : {"code":"503","msg":"内部服务不可用，请稍后再试"}
                return new ByteArrayInputStream(
                        JacksonUtil.toJson(ResultMsg.fail(ResponseEnum.INTERNAL_SERVER_BUSY)).getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                // 设定headers
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}
